package cn.lingyangwl.agile.gateway.filter;

import cn.lingyangwl.agile.gateway.auth.*;
import cn.lingyangwl.agile.gateway.body.*;
import cn.lingyangwl.agile.gateway.config.*;
import cn.lingyangwl.agile.gateway.constants.*;
import cn.lingyangwl.agile.gateway.remote.*;
import cn.lingyangwl.agile.gateway.service.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.module.auth.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.framework.log.common.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 访问权限过滤器
 * @author shenguangyang
 */
@Component
@Slf4j
@AllArgsConstructor
public class AccessAuthFilter implements GlobalFilter, Ordered {
    @Resource
    private MyGatewayProperties myGatewayProperties;

    @Resource
    private MyBearerTokenResolver bearerTokenResolver;

    @Resource
    private MyOpaqueTokenIntrospect opaqueTokenIntrospect;
    @Resource
    private AuthService authService;
    @Resource
    private SystemApi systemApi;

    private static final PathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getPath().value();

        // 校验并提取token值
        HttpHeaders headers = request.getHeaders();

        String token = Optional.ofNullable(headers.getFirst(HttpHeaders.AUTHORIZATION))
                .orElse(headers.getFirst(RequestCons.Headers.WEB_SOCKET_TOKEN));
        if (StringUtils.isNotEmpty(token) && !token.startsWith(OAuth2Cons.TokenType.BASIC)) {
            token = bearerTokenResolver.resolve(exchange);
        }

        // 判断是否为排除的路径
        if (isExcludePathForAuth(uri)) {
            return fillTenantIdToHeader(exchange, chain, token);
        }

        // 校验token并获取当前登录用户信息
        LoginUser loginUser = opaqueTokenIntrospect.introspect(exchange, token);
        GatewayContext.getContext(exchange).setLoginUser(loginUser);

        // 判断是否有权限访问当前路径
        if (isExcludePathForPerm(uri)) {
            String tenantId = getTenantId(exchange);
            return fillLoginUserToRequest(exchange, chain, loginUser, token, tenantId);
        }

        // 校验权限
        authService.hasPermission(exchange, loginUser, uri);

        // 判断请求头中的值
        String tenantId = getTenantId(exchange);
        loginUser.checkTenant(tenantId);
        return fillLoginUserToRequest(exchange, chain, loginUser, token, tenantId);
    }


    /**
     * 设置登录信息到请求头中
     */
    public Mono<Void> fillLoginUserToRequest(ServerWebExchange exchange,
                                            GatewayFilterChain chain, LoginUser loginUser,
                                            String token, String tenantId) {

        // 到这目前是有权限访问当前系统的, 为了避免网络开销, 将权限相关数据清除, 然后放到请求头中传递给下游服务
        AuthUserTenant tenant = loginUser.getTenant();
        loginUser.getAccount().setPassword("");
        tenant.setTenantId(tenantId);
        String currentMainTenantId = loginUser.getTenant().getMainTenantId();
        String mainTenantId = TenantContext.isValidTenantId(currentMainTenantId) ? currentMainTenantId : getMainTenantId(tenantId);

        GatewayContext gatewayContext = (GatewayContext) exchange.getAttributes().get(GatewayContext.CACHE_GATEWAY_CONTEXT);
        if (Objects.isNull(gatewayContext)) {
            gatewayContext = new GatewayContext();
        }
        gatewayContext.setTenantId(tenantId);
        gatewayContext.setMainTenantId(mainTenantId);

        ServerHttpRequest request = exchange.getRequest().mutate()
                .headers(httpHeaders -> {
                    httpHeaders.put(RequestCons.Headers.LOGIN_USER, Collections.singletonList(loginUser.encodeToString()));
                    httpHeaders.put(RequestCons.Headers.TENANT_ID, Collections.singletonList(tenantId));
                    httpHeaders.put(RequestCons.Headers.MAIN_TENANT_ID, Collections.singletonList(mainTenantId));
                    httpHeaders.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(token));
                    httpHeaders.put(LogCons.LOGIN_ID, Collections.singletonList(loginUser.getLoginId()));
                }).build();

        return chain.filter(exchange.mutate().request(request.mutate().build()).build());
    }


    /**
     * 是否为排除的路径
     */
    public boolean isExcludePathForAuth(String uri) {
        return myGatewayProperties.getAuth().getExcludePathForAuth().stream()
                .anyMatch(url -> pathMatcher.match(url, uri));
    }

    public boolean isExcludePathForPerm(String uri) {
        return myGatewayProperties.getAuth().getExcludePathForPerm().stream()
                .anyMatch(url -> pathMatcher.match(url, uri));
    }

    public String getMainTenantId(String tenantId) {
        String mainTenantId;
        try {
            mainTenantId = systemApi.getMainTenantId(tenantId).get(5, TimeUnit.SECONDS);
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            log.error("error: ", e);
            throw new BizException("租户获取失败");
        }
        return mainTenantId;
    }

    /**
     * 填充租户id到请求头中
     */
    public Mono<Void> fillTenantIdToHeader(ServerWebExchange exchange, GatewayFilterChain chain, String token) {
        String tenantId = getTenantId(exchange);
        GatewayContext gatewayContext = (GatewayContext) exchange.getAttributes().get(GatewayContext.CACHE_GATEWAY_CONTEXT);
        if (Objects.isNull(gatewayContext)) {
            gatewayContext = new GatewayContext();
        }
        gatewayContext.setTenantId(tenantId);
        String mainTenantId = getMainTenantId(tenantId);
        gatewayContext.setMainTenantId(mainTenantId);

        ServerHttpRequest newRequest = exchange.getRequest().mutate()
                .headers(httpHeaders -> {
                    httpHeaders.put(RequestCons.Headers.TENANT_ID, Collections.singletonList(tenantId));
                    httpHeaders.put(RequestCons.Headers.MAIN_TENANT_ID, Collections.singletonList(mainTenantId));
                    httpHeaders.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(token));
                }).build();
        return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build());
    }

    public String getTenantId(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        String tenantId = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.TENANT_ID))
                .orElse(request.getQueryParams().getFirst(RequestCons.Param.TENANT_ID));
        if (!isValidTenantId(tenantId)) {
            // 默认使用系统租户id
            return TenantCons.SYSTEM_TENANT_ID + "";
        }
        return tenantId;
    }

    /**
     * 判断是否为有效的用户租户id
     * @param tenantId 租户id
     */
    public boolean isValidTenantId(String tenantId) {
        return !(StringUtils.isEmpty(tenantId) || "null".equals(tenantId) || "undefined".equals(tenantId) || "-1".equals(tenantId));
    }

    @Override
    public int getOrder() {
        return FilterOrderCons.ACCESS_AUTH;
    }
}
